/*
  author Sylvain Bertrand <sylvain.bertrand@gmail.com>
  Protected by linux GNU GPLv2
  Copyright 2012-2014
*/
#include <linux/pci.h>
#include <linux/cdev.h>
#include <asm/unaligned.h>

#include <alga/rng_mng.h>
#include <uapi/alga/pixel_fmts.h>
#include <alga/timing.h>
#include <alga/amd/atombios/atb.h>
#include <uapi/alga/amd/dce6/dce6.h>
#include <alga/amd/atombios/vm.h>
#include <alga/amd/atombios/cm.h>
#include <alga/amd/atombios/pp.h>
#include <alga/amd/atombios/vram_info.h>

#include "../mc.h"
#include "../rlc.h"
#include "../ih.h"
#include "../fence.h"
#include "../ring.h"
#include "../dmas.h"
#include "../ba.h"
#include "../cps.h"
#include "../gpu.h"
#include "../drv.h"

#include "../bif.h"

#include "../cm.h"
#include "../smc.h"
#include "../smc_tbls.h"

#include "../regs.h"

#include "ctx.h"
#include "private.h"

#ifdef CONFIG_ALGA_AMD_SI_DYN_PM_LOG
#define L(fmt,...) printk(KERN_INFO fmt "\n", ##__VA_ARGS__)
void smc_eng_pll_tbl_dump(struct smc_eng_pll_tbl *tbl)
{
	u16 i;

	L("SMC_ENG_PLL_TBL START");

	for (i = 0; i < SMC_ENG_PLL_TBL_STEPS_N; ++i) {
		u32 tmp;

		tmp = get_unaligned_be32(&tbl->freq[i]);
		L("freq[%u]=0x%08x",i,tmp);

		tmp = get_unaligned_be32(&tbl->ss[i]);
		L("ss[%u]=0x%08x",i,tmp);
		
	}

	L("SMC_ENG_PLL_TBL END");
}
#endif

#if 0
static inline u8 do_fit(u32 mask, u32 v)
{
	u8 shift;

	shift = ffs(mask) - 1;
	if ((~(mask >> shift)) & v)
		return 0;
	return 1;
}
#endif

/*
 * actually incremental eng clk pll params, 5120kHz step,
 * SMC_ENG_PLL_TBL_STEPS_N steps
 */
long smc_eng_pll_tbl_init(struct ctx *ctx, struct smc_eng_pll_tbl *tbl)
{
	u16 step;
	u32 eng_clk;

	LOG("smc engine phase locked loop table init");

	eng_clk = 0;
	for (step = 0; step < SMC_ENG_PLL_TBL_STEPS_N; ++step) {
		long r;
		struct eng_pll eng_pll;
		u32 fb_div;
		u32 post_div;
		u32 clk_s;
		u32 clk_v;
		u32 freq;
		u32 ss;

		r = eng_pll_compute(ctx, eng_clk, &eng_pll);
		if (r == -SI_ERR)
			return -SI_ERR;

		post_div = get(CEPFC0_PDIV_A, eng_pll.cg_eng_pll_func_ctl_0);
		fb_div = get(CEPFC2_FB_DIV, eng_pll.cg_eng_pll_func_ctl_2);
		clk_s = get(CEPS0_CLK_S, eng_pll.cg_eng_pll_ss_0);
		clk_v = get(CEPS1_CLK_V, eng_pll.cg_eng_pll_ss_1);

		/* adjust values */
		fb_div &= ~0x000001fff;
		fb_div >>= 1;
		clk_v >>= 6;	

#if 0
		/*
		 * check the value fits: we are not tolerant like upstream,
		 * failure will make the entire init process fail
		 */
		if (!do_fit(SMC_ENG_PLL_TBL_FREQ_FB_DIV, fb_div)) {
			dev_err(&ctx->dev->dev, "dyn_pm:feedback divider too big for engine clock pll table\n");
			return -SI_ERR;
		}
		if (!do_fit(SMC_ENG_PLL_TBL_FREQ_P_DIV, post_div)) {
			dev_err(&ctx->dev->dev, "dyn_pm:post divider too big for engine clock pll table\n");
			return -SI_ERR;
		}
		if (!do_fit(SMC_ENG_PLL_TBL_SS_CLK_V, clk_v)) {
			dev_err(&ctx->dev->dev, "dyn_pm:clock v too big for engine clock pll table\n");
			return -SI_ERR;
		}
		if (!do_fit(SMC_ENG_PLL_TBL_SS_CLK_S, clk_s)) {
			dev_err(&ctx->dev->dev, "dyn_pm:clock s too big for engine clock pll table\n");
			return -SI_ERR;
		}
#endif

		freq = set(SMC_ENG_PLL_TBL_FREQ_FB_DIV, fb_div);
		freq |= set(SMC_ENG_PLL_TBL_FREQ_P_DIV, post_div);
		put_unaligned_be32(freq, &tbl->freq[step]);
	
		ss = set(SMC_ENG_PLL_TBL_SS_CLK_V, clk_v);
		ss |= set(SMC_ENG_PLL_TBL_SS_CLK_S, clk_s);
		put_unaligned_be32(ss, &tbl->ss[step]);

		eng_clk += SMC_ENG_PLL_TBL_STEP_FREQ;
	}
	return 0;
}
